import threading
import uuid
import json
import requests

from apscheduler.schedulers.background import BackgroundScheduler

from schema.cron_job import CronJobCreate, CronJobUpdate, CronJobDelete, CronJobFilter, Cron
from eval_lib.common import logger
from eval_lib.model.mysql.models import CronJob
from eval_lib.model.mysql import const as db_const
from schema.auto_test import AutoTestCreate
from common.utils import Paginator

log = logger.get_logger()
CRON_JOB_LOCK = threading.RLock()


class CronJobService(object):

    def __init__(self):
        self.scheduler = None
        self.scheds = {}

    def init(self):
        self.scheduler = BackgroundScheduler()
        self.scheduler.start()
        cjs, _ = self.Get()
        for cj in cjs:
            if cj.active == 1:
                self.add_job(cj)

    def Post(self, info: CronJobCreate):
        """
        创建一个新的定时任务。

        该方法通过接收一个CronJobCreate类型的info参数，来创建一个新的定时任务。它生成一个唯一的任务UUID，
        根据info中的信息初始化一个CronJob对象，并将该对象保存到数据库中。随后，它将这个新任务添加到调度器中，
        并返回新创建的定时任务的详细信息。

        参数:
        - info: CronJobCreate类型的实例，包含了新任务的名称、cron表达式、变量、分支和激活状态等信息。

        返回:
        - 返回新创建的定时任务的详细信息。
        """
        # 使用互斥锁确保并发安全
        with CRON_JOB_LOCK:
            # 生成唯一的任务UUID
            job_uuid = str(uuid.uuid4())
            # 创建CronJob对象，并初始化其属性
            cj = CronJob(
                uuid=job_uuid, name=info.name, cron=info.cron,
                variables=json.dumps(info.variables.to_json()),
                branch=info.branch, active=info.active
            )
            # 将CronJob对象保存到数据库
            cj.save()
            # 将新任务添加到调度器
            self.add_job(cj)
            # 返回新创建的定时任务的信息
            return self.Get(CronJobFilter(uuid=job_uuid))

    def Update(self, info: CronJobUpdate):
        """
        更新Cron作业的信息。

        使用互斥锁确保在更新作业信息时的线程安全。根据info中的active字段决定是添加还是移除作业。
        如果info中包含变量信息，将其序列化后更新到数据库中。最后，根据uuid获取更新后的Cron作业信息并返回。

        参数:
        info: CronJobUpdate类型的实例，包含需要更新的Cron作业的信息。
        
        返回:
        更新后的Cron作业信息。
        """
        # 使用互斥锁确保更新操作的原子性
        with CRON_JOB_LOCK:
            # 根据info中的active字段决定是添加还是移除作业
            if info.active is not None:
                if info.active == 1:
                    # 添加新的Cron作业
                    cj = self.Get(CronJobFilter(uuid=info.uuid))
                    if not cj:
                        return
                    self.add_job(cj)
                else:
                    # 移除指定的Cron作业
                    self.remove_job(info.uuid)

            # 将info转换为JSON格式的数据
            data = info.to_json()

            # 如果info中包含变量信息，将其序列化并添加到data中
            if info.variables:
                data["variables"] = json.dumps(info.variables.to_json())

            # 移除data中的uuid字段，因为它将作为条件用于更新数据库中的相应记录
            data.pop("uuid")

            # 更新数据库中uuid与info.uuid相同的Cron作业记录，并执行更新操作
            CronJob.update(**data).where(CronJob.uuid == uuid).execute()

            # 根据uuid获取并返回更新后的Cron作业信息
            return self.Get(CronJobFilter(uuid=info.uuid))

    def Delete(self, info: CronJobDelete):
        """
        删除指定的定时任务。

        使用互斥锁确保并发安全，遍历待删除任务的UUID列表，如果该UUID对应的任务存在于调度器中，
        则从调度器中移除该任务。然后更新数据库中对应UUID的定时任务的删除标志。

        参数:
        - info: CronJobDelete类型的实例，包含待删除任务的UUID列表。

        返回:
        - 返回更新后的定时任务列表。
        """
        # 使用互斥锁确保并发删除操作的安全性
        with CRON_JOB_LOCK:
            # 遍历待删除的任务UUID列表
            for job_uuid in info.uuids:
                # 检查UUID是否存在于当前的调度任务中
                if job_uuid in self.scheds:
                    # 如果存在，则从调度器中移除该任务
                    self.remove_job(job_uuid)
            # 更新数据库中对应UUID的定时任务，将其标记为已删除
            CronJob.update(deleted=1).where(CronJob.uuid == uuid).execute()
        # 返回更新后的所有定时任务列表
        return self.Get()

    def Get(self, info: CronJobFilter = None) -> list:
        """
        根据提供的过滤信息获取CronJob列表。

        如果提供了过滤信息（info不为None），则根据过滤条件、分页信息查询并返回CronJob列表；
        如果没有提供过滤信息，则返回所有未删除的CronJob列表。

        :param info: 包含过滤条件和分页信息的CronJobFilter对象，可选。
        :return: 一个包含CronJob实体的列表和分页信息的元组。
        """
        # 初始化一个空列表用于存储查询结果
        cjs = []

        # 定义不删除状态的查询条件
        not_delted_where_clause = CronJob.deleted == db_const.CRON_JOB_NOT_DELETED
        # 定义查询结果的排序方式为按创建时间降序
        order_by = CronJob.created_at.desc()

        # 如果提供了过滤信息
        if info:
            # 根据提供的分页信息对查询结果进行分页
            page = Paginator(
                CronJob.select().where(not_delted_where_clause),
                info.page_index, info.page_size
            )

            # 将过滤信息转换为JSON格式
            json_where = info.to_json()
            # 根据JSON格式的过滤信息生成查询条件
            where_clause = CronJob.visible_where_clause(json_where)
            # 如果过滤信息中未标记为删除，则合并不删除状态的查询条件
            if not json_where.get("deleted"):
                where_clause = (where_clause) & (
                    not_delted_where_clause
                ) if where_clause else not_delted_where_clause
            # 执行查询并根据排序方式和分页信息限制查询结果
            cjs = CronJob.select().where(where_clause
                                         ).order_by(order_by).limit(
                                             page.limit
                                         ).offset(page.offset)
            # 返回查询结果列表和分页信息
            return [cj for cj in cjs], page
        else:
            # 如果没有提供过滤信息，查询所有未删除的CronJob
            cjs = CronJob.select().where(not_delted_where_clause
                                         ).order_by(order_by)
            # 返回查询结果列表和一个空的分页信息对象
            return [cj for cj in cjs], Paginator()

    def send_auto_test_msg(self, cj: CronJob):

        def _send_auto_test_msg():
            case_name = cj.variables.get("case_name", "")
            process_num = cj.variables.get("process_num", 1)
            agent_type = cj.variables.get("agent_type", "")
            agent_config = cj.variables.get("agent_config", {})
            case_variables = cj.variables.get("case_variables", {})
            msg = AutoTestCreate(
                case_name=case_name, process_num=process_num,
                runner_image_tag=cj.branch, agent_type=agent_type,
                agent_config=agent_config, case_variables=case_variables,
                user=cj.user, cron_id=cj.id
            )
            print(msg)
            url = "http://localhost:10083/v1/evaluation/auto-test"
            body = msg.to_json()
            headers = {"Content-Type": "application/json"}
            res = requests.post(url, data=json.dumps(body), headers=headers)
            if res.status_code == 200:
                log.info(f"Cron Job Post Auto-Test Success {res.json()}")
            else:
                raise Exception(
                    f"Cron Job Post Auto-Test error: {res.status_code}"
                )

        return _send_auto_test_msg

    def add_job(self, cj: CronJob):
        cron = Cron(cron=str(cj.cron))
        job_id = self.scheduler.add_job(
            self.send_auto_test_msg(cj), 'cron', minute=cron.minute,
            hour=cron.hour, day=cron.day, month=cron.month, week=cron.week
        ).id
        self.scheds[cj.uuid] = job_id

    def remove_job(self, job_uuid):
        if job_uuid not in self.scheds:
            log.warning(f"cron job uuid({job_uuid}) not in scheds")
            return
        job_id = self.scheds[job_uuid]
        self.scheduler.remove_job(job_id)
        del self.scheds[job_uuid]


CronJobWorker = CronJobService()
